import { newArrayProto } from "./array";
import Dep from "./dep";

class Observer {
    constructor(data) {
        // 给每个对象和数组都增加 dep 属性，后续添加了属性，可以触发 dep 进行更新
        this.dep = new Dep();

        // 将 Observer 实例挂载到 data 上，给数据添加了一个标识，如果数据上有这个属性，则表示这个数据已经被劫持
        Object.defineProperty(data, "__ob__", {
            value: this, // Observer 实例
            enumerable: false, // 不可枚举
        })

        // 判断当前 data 是一个对象还是一个数组
        if (Array.isArray(data)) {
            // 重写数组原型中的 7 个变异方法(会改变原数组的方法)，使其能够触发劫持
            data.__proto__ = newArrayProto;
            // 观察数组
            this.observeArray(data);
        } else {
            // 观察对象
            this.observeObject(data);
        }
    }
    observeObject(data) {
        // 循环对 对象的属性依次进行劫持
        const keys = Object.keys(data);
        keys.forEach(key => defineReactive(data, key, data[key]));
    }
    observeArray(data) {
        // 循环对 数组内的对象的属性进行劫持
        data.forEach(item => {
            observe(item);
        })
    }
}

function dependArray(arr) {
    // 循环数组中的每一项，收集依赖
    arr.forEach(item => {
        // 若当前数组上存在 ob 则触发 depend 收集对应的 watcher
        item.__ob__?.dep.depend();
        // 继续递归查询属性是否是数组
        if (Array.isArray(item)) {
            dependArray(item);
        }
    })
}

export function defineReactive(target, key, value) {
    // value 可能是对象，可以进行递归调用深度劫持
    // 获取子属性的 observer 实例
    let childOb = observe(value);

    // 给每一个属性添加一个 dep 属性，用来收集 watcher
    let dep = new Dep();

    /*
        Object.defineProperty 只能劫持已经存在的属性，不能劫持不存在的属性
        对属性进行"重新的定义"，添加 getter 和 setter 属性
        取值的时候，执行 get 方法
        设置或更新值的时候，执行 set 方法
    */
    Object.defineProperty(target, key, {
        get() {
            // 如果 Dep.target 存在，说明当前正在触发 watcher 的 get 方法
            if (Dep.target) {
                // 则调用 dep.depend 方法收集对应的 watcher
                dep.depend();

                // 如果子属性的 observer 实例存在，则调用子属性的 dep.depend 方法
                if (childOb) {
                    // 如果子属性存在，则调用子属性的 dep.depend 方法收集对应的 watcher
                    childOb.dep.depend();

                    // 判断数组内的属性是否是数组，若是则递归收集对应的 watcher
                    if (Array.isArray(value)) {
                        dependArray(value);
                    }
                }
            }
            return value;
        },
        set(newVal) {
            // 若是新值和旧值相同，则不做任何处理
            if (newVal === value) return;

            // 内部函数使用外部函数的参数，这里会产生一个闭包
            value = newVal;

            // 属性值发生改变，dep 通知对应的 watcher 更新数据
            dep.notify();
        },
    })
}

export function observe(data) {
    // 只对对象进行劫持
    if (typeof data !== 'object' || data == null) return;

    // 如果一个对象已经被劫持过了，则直接返回这个实例
    if (data.__ob__ instanceof Observer) return data.__ob__;

    // 判断对象是否被劫持，可以实现一个实例，用实例来判断是否被劫持
    return new Observer(data);
}